// FleetOrders.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;

using Supremacy.AI;
using Supremacy.Annotations;
using Supremacy.Economy;
using Supremacy.Entities;
using Supremacy.Game;
using Supremacy.Pathfinding;
using Supremacy.Resources;
using Supremacy.Tech;
using Supremacy.Types;
using Supremacy.Universe;
using Supremacy.Utility;

using Wintellect.PowerCollections;

namespace Supremacy.Orbitals
{
    [Serializable]
    public static class FleetOrders
    {
        public static readonly FleetOrder EngageOrder;
        public static readonly FleetOrder AvoidOrder;
        public static readonly FleetOrder ColonizeOrder;
        public static readonly FleetOrder TowOrder;
        public static readonly FleetOrder CollectDeuteriumOrder;
        public static readonly FleetOrder EscortOrder;
        public static readonly FleetOrder BuildStationOrder;
        public static readonly FleetOrder ExploreOrder;

        private static readonly List<FleetOrder> _orders;

        static FleetOrders()
        {
            EngageOrder = new EngageOrder();
            AvoidOrder = new AvoidOrder();
            ColonizeOrder = new ColonizeOrder();
            TowOrder = new TowOrder();
            CollectDeuteriumOrder = new CollectDeuteriumOrder();
            EscortOrder = new EscortOrder();
            BuildStationOrder = new BuildStationOrder();
            ExploreOrder = new ExploreOrder();

            _orders = new List<FleetOrder>
                      {
                          EngageOrder,
                          AvoidOrder,
                          ColonizeOrder,
                          TowOrder,
                          CollectDeuteriumOrder,
                          EscortOrder,
                          BuildStationOrder,
                          //ExploreOrder
                      };
        }

        public static ICollection<FleetOrder> GetAvailableOrders(Fleet fleet)
        {
            return _orders.Where(o => (o.GetType() == fleet.Order.GetType()) || o.IsValidOrder(fleet)).Select(o => o.Create()).ToList();
        }
    }

    #region Engage Order
    [Serializable]
    public sealed class EngageOrder : FleetOrder
    {
        public override string OrderName
        {
            get { return ResourceManager.GetString("FLEET_ORDER_ENGAGE"); }
        }

        public override string Status
        {
            get { return ResourceManager.GetString("FLEET_ORDER_ENGAGE"); }
        }

        public override bool IsValidOrder(Fleet fleet)
        {
            return (base.IsValidOrder(fleet) && fleet.IsCombatant);
        }

        public override FleetOrder Create()
        {
            return new EngageOrder();
        }
    }
    #endregion

    #region Avoid Order
    [Serializable]
    public sealed class AvoidOrder : FleetOrder
    {
        public override string OrderName
        {
            get { return ResourceManager.GetString("FLEET_ORDER_AVOID"); }
        }

        public override string Status
        {
            get { return ResourceManager.GetString("FLEET_ORDER_AVOID"); }
        }

        public override bool WillEngageHostiles
        {
            get { return false; }
        }

        public override FleetOrder Create()
        {
            return new AvoidOrder();
        }
    }
    #endregion

    #region ColonizeOrder
    [Serializable]
    public sealed class ColonizeOrder : FleetOrder
    {
        private readonly bool _isComplete;

        public override string OrderName
        {
            get { return ResourceManager.GetString("FLEET_ORDER_COLONIZE"); }
        }

        public override string Status
        {
            get { return ResourceManager.GetString("FLEET_ORDER_COLONIZE"); }
        }

        public override FleetOrder Create()
        {
            return new ColonizeOrder();
        }

        public override bool IsComplete
        {
            get { return _isComplete; }
        }

        public override bool IsCancelledOnRouteChange
        {
            get { return true; }
        }

        public override bool IsRouteCancelledOnAssign
        {
            get { return true; }
        }

        public override bool WillEngageHostiles
        {
            get { return false; }
        }

        public ColonizeOrder()
        {
            _isComplete = false;
        }

        private Ship FindBestColonyShip()
        {
            Ship bestShip = null;
            foreach (Ship ship in Fleet.Ships)
            {
                if (ship.ShipType == ShipType.Colony)
                {
                    if ((bestShip == null)
                        || (ship.ShipDesign.WorkCapacity > bestShip.ShipDesign.WorkCapacity))
                    {
                        bestShip = ship;
                    }
                }
            }
            return bestShip;
        }

        public override bool IsValidOrder(Fleet fleet)
        {
            if (!base.IsValidOrder(fleet))
                return false;
            if (fleet.Sector.System == null)
                return false;
            if (fleet.Sector.System.HasColony)
                return false;
            //if (fleet.Sector.IsOwned && (fleet.Sector.Owner != fleet.Owner))
            //    return false;
            if (!fleet.Sector.System.IsHabitable(fleet.Owner.Race))
                return false;
            foreach (var ship in fleet.Ships)
            {
                if (ship.ShipType == ShipType.Colony)
                    return true;
            }
            return false;
        }

        protected internal override void OnTurnBeginning()
        {
            base.OnTurnBeginning();
            if (_isComplete)
                return;
            var colonyShip = FindBestColonyShip();
            if (colonyShip == null)
                return;
            CreateColony(Fleet.Owner,
                         Fleet.Sector.System,
                         colonyShip.ShipDesign.WorkCapacity);
            GameContext.Current.Universe.Destroy(colonyShip);
        }

        protected internal override void OnOrderAssigned()
        {
            base.OnOrderAssigned();
            if (!Fleet.Route.IsEmpty)
                Fleet.Route = TravelRoute.Empty;
        }

        private static void CreateColony(Civilization civ, StarSystem system, int population)
        {
            var colony = new Colony(system, civ.Race);
            var civManager = GameContext.Current.CivilizationManagers[civ.Key];
            var baseMoraleTable = GameContext.Current.Tables.MoraleTables["BaseMoraleLevels"];

            colony.ObjectID = GameContext.Current.GenerateID();
            colony.Population.BaseValue = population;
            colony.Population.Reset();
            colony.Name = system.Name;
            colony.Owner = civ;

            system.Owner = civ;
            system.Colony = colony;

            GameContext.Current.Universe.Objects.Add(colony);
            civManager.Colonies.Add(colony);

            if (baseMoraleTable[civ.Key] != null)
                colony.Morale.BaseValue = Number.ParseInt32(baseMoraleTable[civ.Key][0]);
            else
                colony.Morale.BaseValue = Number.ParseInt32(baseMoraleTable[0][0]);

            colony.Morale.Reset();

            ColonyBuilder.Build(colony);

            civManager.MapData.SetScanned(colony.Location, true, 1);
            civManager.ApplyMoraleEvent(MoraleEvent.ColonizeSystem, system.Location);
            civManager.SitRepEntries.Add(new NewColonySitRepEntry(civ, colony));
        }
    }
    #endregion

    #region TowOrder
    [Serializable]
    public sealed class TowOrder : FleetOrder
    {
        private int _targetFleetId = GameObjectID.InvalidID;
        private bool _shipsLocked;
        private bool _orderLocked;
        private FleetOrder _lastOrder;

        public override object Target
        {
            get { return TargetFleet; }
            set
            {
                if (value == null)
                    TargetFleet = null;
                if (value is Fleet)
                    TargetFleet = value as Fleet;
                else
                    throw new ArgumentException("Target must be of type Supremacy.Orbitals.Fleet");
                OnPropertyChanged("Target");
            }
        }

        public Fleet TargetFleet
        {
            get { return GameContext.Current.Universe.Objects[_targetFleetId] as Fleet; }
            private set
            {
                var currentTarget = this.TargetFleet;
                if (currentTarget != null)
                    EndTow();
                if (value == null)
                    _targetFleetId = GameObjectID.InvalidID;
                else
                    _targetFleetId = value.ObjectID;
                OnPropertyChanged("TargetFleet");
            }
        }

        public override string OrderName
        {
            get { return ResourceManager.GetString("FLEET_ORDER_TOW"); }
        }

        public override string Status
        {
            get
            {
                return String.Format(
                    ResourceManager.GetString("FLEET_ORDER_STATUS_TOW"),
                    TargetFleet);
            }
        }

        public override string DisplayText
        {
            get
            {
                if (!Fleet.Route.IsEmpty)
                {
                    int turns = Fleet.Route.Length / Fleet.Speed;
                    string formatString;
                    if ((Fleet.Route.Length % Fleet.Speed) != 0)
                        turns++;
                    if (turns == 1)
                        formatString = ResourceManager.GetString("ORDER_ETA_TURN_MULTILINE");
                    else
                        formatString = ResourceManager.GetString("ORDER_ETA_TURNS_MULTILINE");
                    return String.Format(formatString, Status, turns);
                }
                return Status;
            }
        }

        public override bool WillEngageHostiles
        {
            get { return false; }
        }

        public override bool IsComplete
        {
            get
            {
                var targetFleet = this.TargetFleet;
                return (targetFleet != null) && targetFleet.IsInTow && !targetFleet.IsStranded && Fleet.Route.IsEmpty;
            }
        }

        public override FleetOrder Create()
        {
            return new TowOrder();
        }

        public override bool IsTargetRequired(Fleet fleet)
        {
            return true;
        }

        private void BeginTow()
        {
            if (TargetFleet.IsInTow)
                return;

            TargetFleet.IsInTow = true;

            if (!TargetFleet.Order.IsCancelledOnRouteChange)
            {
                _lastOrder = TargetFleet.Order;
                _orderLocked = TargetFleet.IsOrderLocked;
            }

            _shipsLocked = TargetFleet.AreShipsLocked;

            TargetFleet.LockShips();
                
            if (_orderLocked)
                TargetFleet.UnlockOrder();

            TargetFleet.SetOrder(FleetOrders.AvoidOrder.Create());
            TargetFleet.LockOrder();

            if (TargetFleet.IsRouteLocked)
                TargetFleet.UnlockRoute();

            TargetFleet.SetRoute(TravelRoute.Empty);
            TargetFleet.LockRoute();

            Fleet.LockShips();
        }

        private void EndTow()
        {
            if (!TargetFleet.IsInTow)
                return;

            TargetFleet.UnlockOrder();
            TargetFleet.UnlockRoute();

            if (_lastOrder != null)
                TargetFleet.SetOrder(_lastOrder);
            else
                TargetFleet.SetOrder(TargetFleet.GetDefaultOrder());

            if (_orderLocked)
                TargetFleet.LockOrder();
            if (!_shipsLocked)
                TargetFleet.UnlockShips();

            TargetFleet.IsInTow = false;

            Fleet.UnlockShips();
        }

        protected internal override void OnOrderAssigned()
        {
            base.OnOrderAssigned();
            if (TargetFleet != null)
                BeginTow();
        }

        protected internal override void OnOrderCancelled()
        {
            if (TargetFleet != null)
                EndTow();
            base.OnOrderCancelled();
        }

        protected internal override void OnOrderCompleted()
        {
            if (TargetFleet != null)
                EndTow();
            base.OnOrderCompleted();
        }

        protected internal override void OnTurnBeginning()
        {
            base.OnTurnBeginning();
         
            var targetFleet = this.TargetFleet;
            if ((targetFleet != null) && targetFleet.IsInTow)
                TargetFleet.SetRoute(TravelRoute.Empty);
        }

        protected internal override void OnTurnEnding()
        {
            base.OnTurnEnding();

            var targetFleet = this.TargetFleet;
            var civManager = GameContext.Current.CivilizationManagers[Fleet.OwnerID];
            int fuelRange = civManager.MapData.GetFuelRange(Fleet.Location);

            if (targetFleet != null)
            {
                var ship = targetFleet.Ships.SingleOrDefault();
                if ((ship != null) && (targetFleet.Range >= fuelRange))
                {
                    int fuelNeeded = ship.FuelReserve.Maximum - ship.FuelReserve.CurrentValue;
                    ship.FuelReserve.AdjustCurrent(
                        civManager.Resources[ResourceType.Deuterium].AdjustCurrent(-fuelNeeded));
                }
            }

            if (this.IsComplete)
                Fleet.SetOrder(Fleet.GetDefaultOrder());
        }

        protected internal override void OnFleetMoved()
        {
            base.OnFleetMoved();
            if (TargetFleet != null)
                TargetFleet.Location = Fleet.Location;
        }

        public override bool IsValidOrder(Fleet fleet)
        {
            if (!base.IsValidOrder(fleet))
                return false;
            if (fleet.Ships.Count != 1)
                return false;
            if (fleet == Fleet && fleet.IsStranded)
                return false;
            return true;
        }

        public override IEnumerable<object> FindTargets(Fleet source)
        {
            var targets = new List<Object>();
            foreach (var targetFleet in GameContext.Current.Universe.FindAt<Fleet>(source.Location))
            {
                if ((targetFleet != source)
                    && (targetFleet.Owner == source.Owner)
                    && (targetFleet.Ships.Count == 1)
                    && targetFleet.IsStranded)
                {
                    targets.Add(targetFleet);
                }
            }
            return targets;
        }
    }
    #endregion

    #region Collect Deuterium Order
    [Serializable]
    public sealed class CollectDeuteriumOrder : FleetOrder
    {
        private int _turnsCollecting;

        public override string OrderName
        {
            get { return ResourceManager.GetString("FLEET_ORDER_COLLECT_DEUTERIUM"); }
        }

        public override string Status
        {
            get { return ResourceManager.GetString("FLEET_ORDER_COLLECT_DEUTERIUM"); }
        }

        public override bool IsCancelledOnRouteChange
        {
            get { return true; }
        }

        public override bool IsRouteCancelledOnAssign
        {
            get { return true; }
        }

        public override bool WillEngageHostiles
        {
            get { return false; }
        }

        public override FleetOrder Create()
        {
            return new CollectDeuteriumOrder();
        }

        public override bool IsValidOrder(Fleet fleet)
        {
            if (!base.IsValidOrder(fleet))
                return false;
            if (!FleetHelper.IsFleetInFuelRange(fleet))
            {
                bool needsFuel = false;
                foreach (var ship in fleet.Ships)
                {
                    if (ship.FuelReserve.IsMaximized)
                        continue;
                    needsFuel = true;
                    break;
                }
                if (needsFuel)
                {
                    var system = fleet.Sector.System;
                    if (system != null)
                        return ((system.StarType == StarType.Nebula) || system.ContainsPlanetType(PlanetType.GasGiant));
                }
            }
            return false;
        }

        protected internal override void OnTurnBeginning()
        {
            base.OnTurnBeginning();

            if ((++_turnsCollecting % 2) != 0)
                return;

            foreach (var ship in Fleet.Ships)
                ship.FuelReserve.AdjustCurrent(1);
        }
    }
    #endregion

    #region Escort Order
    [Serializable]
    public sealed class EscortOrder : FleetOrder
    {
        int _fuelRemaining;
        bool _assigned;
        int _targetFleetId = GameObjectID.InvalidID;

        public override object Target
        {
            get { return TargetFleet; }
            set
            {
                if ((value != null) && !(value is Fleet))
                    throw new ArgumentException("Target must be of type Supremacy.Orbitals.Fleet");
                TargetFleet = value as Fleet;
            }
        }

        public Fleet TargetFleet
        {
            get { return GameContext.Current.Universe.Objects[_targetFleetId] as Fleet; }
            private set
            {
                if (value == null)
                    _targetFleetId = GameObjectID.InvalidID;
                else
                    _targetFleetId = value.ObjectID;
            }
        }

        public override string OrderName
        {
            get { return ResourceManager.GetString("FLEET_ORDER_ESCORT"); }
        }

        public override string Status
        {
            get
            {
                return String.Format(
                    ResourceManager.GetString("FLEET_ORDER_STATUS_ESCORT"),
                    TargetFleet);
            }
        }

        public override bool IsCancelledOnRouteChange
        {
            get { return _assigned; }
        }

        public override bool IsRouteCancelledOnAssign
        {
            get { return true; }
        }

        public override bool WillEngageHostiles
        {
            get { return false; }
        }

        public override bool IsTargetRequired(Fleet fleet)
        {
            return true;
        }

        public override FleetOrder Create()
        {
            return new EscortOrder();
        }

        private void BeginEscort()
        {
            if (_assigned)
                return;
            Fleet.SetRouteInternal(TravelRoute.Empty);
            _assigned = true;
            TargetFleet.PropertyChanged += TargetFleet_PropertyChanged;
        }

        void TargetFleet_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!String.Equals(e.PropertyName, "Location"))
                return;
            if (Fleet.IsStranded)
                return;
            var route = AStar.FindPath(Fleet, TargetFleet.Sector);
            if (route.IsEmpty)
                return;
            Fleet.SetRouteInternal(route);
            while (!Fleet.IsStranded && (_fuelRemaining > 0))
            {
                if (!Fleet.MoveAlongRoute())
                    break;
                if (!FleetHelper.IsFleetInFuelRange(Fleet))
                    --_fuelRemaining;
            }
            Fleet.SetRouteInternal(TravelRoute.Empty);
        }

        private void EndEscort()
        {
            if (!_assigned)
                return;
            TargetFleet.PropertyChanged -= TargetFleet_PropertyChanged;
            _assigned = false;
        }

        protected internal override void OnOrderAssigned()
        {
            base.OnOrderAssigned();
            if (TargetFleet != null)
                BeginEscort();
        }

        protected internal override void OnOrderCancelled()
        {
            base.OnOrderCancelled();
            if (TargetFleet != null)
                EndEscort();
        }

        protected internal override void OnOrderCompleted()
        {
            base.OnOrderCompleted();
            if (TargetFleet != null)
                EndEscort();
        }

        public override bool IsValidOrder(Fleet fleet)
        {
            if (!base.IsValidOrder(fleet))
                return false;
            if (!fleet.IsCombatant)
                return false;
            if (fleet.IsStranded)
                return false;
            if ((fleet == Fleet)
                && (TargetFleet != null)
                && (fleet.Speed < TargetFleet.Speed))
            {   
                return false;
            }
            return true;
        }

        public static bool IsEscorting(Fleet sourceFleet, Fleet targetFleet)
        {
            if ((sourceFleet == null) || (targetFleet == null))
                return false;
            var escortOrder = sourceFleet.Order as EscortOrder;
            if (escortOrder == null)
                return false;
            return Equals(escortOrder.Target, targetFleet);
        }

        public override IEnumerable<object> FindTargets(Fleet source)
        {
            return GameContext.Current.Universe.FindAt<Fleet>(source.Location)
                .Where(fleet => fleet != source)
                .Where(fleet => !fleet.IsInTow)
                .Where(fleet => !fleet.IsStranded)
                .Where(fleet => !IsEscorting(fleet, source))
                .Cast<object>();
        }

        protected internal override void UpdateReferences()
        {
            base.UpdateReferences();
            if (_assigned && (TargetFleet != null))
                TargetFleet.PropertyChanged += TargetFleet_PropertyChanged;
        }

        protected internal override void OnTurnBeginning()
        {
            base.OnTurnBeginning();
            _fuelRemaining = Fleet.Speed;
        }

        protected internal override void OnTurnEnding()
        {
            base.OnTurnEnding();
            _fuelRemaining = 0;
        }
    }
    #endregion

    #region Build Station Order
    [Serializable]
    public sealed class BuildStationOrder : FleetOrder
    {
        private static readonly GameLog s_log = GameLog.GetLog(typeof(BuildStationOrder));

        private bool _finished;
        private StationBuildProject _buildProject;

        public StationDesign StationDesign
        {
            get { return BuildProject.BuildDesign as StationDesign; }
        }

        public override string OrderName
        {
            get { return ResourceManager.GetString("FLEET_ORDER_BUILD_STATION"); }
        }

        public override string Status
        {
            get
            {
                return String.Format(
                    ResourceManager.GetString("FLEET_ORDER_STATUS_BUILD_STATION"),
                    ResourceManager.GetString(_buildProject.StationDesign.Name));
            }
        }

        public override string TargetDisplayMember
        {
            get { return "BuildDesign.LocalizedName"; }
        }

        public override object Target
        {
            get { return BuildProject; }
            set { BuildProject = value as StationBuildProject; }
        }

        public StationBuildProject BuildProject
        {
            get { return _buildProject; }
            set { _buildProject = value; }
        }

        public override Percentage? PercentComplete
        {
            get
            {
                if (BuildProject != null)
                    return BuildProject.PercentComplete;
                return null;
            }
        }

        public override bool IsCancelledOnRouteChange
        {
            get { return true; }
        }

        public override bool IsRouteCancelledOnAssign
        {
            get { return true; }
        }

        public override bool IsComplete
        {
            get { return (BuildProject != null) && BuildProject.IsCompleted; }
        }

        public override FleetOrder Create()
        {
            return new BuildStationOrder();
        }

        public override IEnumerable<object> FindTargets([NotNull] Fleet source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            var designs = new List<StationDesign>();
            var targets = new List<object>();
            var civManager = GameContext.Current.CivilizationManagers[source.Owner];

            if (civManager == null)
            {
                s_log.General.WarnFormat(
                    "Failed to load CivilizationManager for fleet owner (fleet ID = {0}, owner ID = {1})",
                    source.ObjectID,
                    (source.Owner != null) ? source.Owner.ShortName : source.OwnerID.ToString());
                return targets;
            }

            foreach (var stationDesign in civManager.TechTree.StationDesigns)
            {
                if (TechTreeHelper.MeetsTechLevels(civManager, stationDesign))
                    designs.Add(stationDesign);
            }

            for (int i = 0; i < designs.Count; i++)
            {
                for (int j = 0; j < designs.Count; j++)
                {
                    if (i == j)
                        continue;
                    foreach (var obsoleteDesign in designs[i].ObsoletedDesigns)
                    {
                        if (obsoleteDesign != designs[j])
                            continue;
                        designs.RemoveAt(j);
                        if (i > j)
                            i--;
                        j--;
                    }
                }
            }

            foreach (var design in designs)
                targets.Add(new StationBuildProject(new FleetProductionCenter(source), design));

            return targets;
        }

        public override bool IsValidOrder(Fleet fleet)
        {
            if (fleet.Sector.Station != null)
                return false;
            if (fleet.Sector.IsOwned && (fleet.Sector.Owner != fleet.Owner))
                return false;
            foreach (var otherFleet in GameContext.Current.Universe.FindAt<Fleet>(fleet.Location))
            {
                if ((otherFleet != fleet) && (otherFleet.Order is BuildStationOrder))
                    return false;
            }
            foreach (var ship in fleet.Ships)
            {
                if (ship.ShipType == ShipType.Construction)
                    return true;
            }
            return false;
        }

        public override bool IsTargetRequired(Fleet fleet)
        {
            return true;
        }

        protected internal override void OnTurnBeginning()
        {
            base.OnTurnBeginning();

            if (!IsAssigned)
                return;
            
            var project = _buildProject;
            if ((project == null) || (project.ProductionCenter == null) || project.IsCompleted)
                return;

            var civManager = GameContext.Current.CivilizationManagers[project.Builder];
            if (civManager == null)
            {
                var owner = project.ProductionCenter.Owner;
                s_log.General.WarnFormat(
                    "Failed to load CivilizationManager for build project owner (build project ID = {0}, owner ID = {1})",
                    project.ProductionCenter.ObjectID,
                    (owner != null) ? owner.ShortName : "null");
                return;
            }

            var buildOutput = project.ProductionCenter.GetBuildOutput(0);
            var resources = new ResourceValueCollection();

            //resources[ResourceType.Personnel] = civManager.Personnel[PersonnelCategory.Officers].CurrentValue;
            resources[ResourceType.RawMaterials] = civManager.Resources[ResourceType.RawMaterials].CurrentValue;

            var usedResources = resources.Clone();

            project.Advance(ref buildOutput, usedResources);

            //civManager.Personnel[PersonnelCategory.Officers].AdjustCurrent(
            //    usedResources[ResourceType.Personnel] - resources[ResourceType.Personnel]);
            civManager.Resources[ResourceType.RawMaterials].AdjustCurrent(
                usedResources[ResourceType.RawMaterials] - resources[ResourceType.RawMaterials]);
        }

        protected internal override void OnOrderCompleted()
        {
            base.OnOrderCompleted();

            if (!_finished && (BuildProject != null))
            {
                BuildProject.Finish();
                _finished = true;
            }

            var destroyedShip = Fleet.Ships.Where(o => o.ShipType == ShipType.Construction).FirstOrDefault();
            if (destroyedShip != null)
                GameContext.Current.Universe.Destroy(destroyedShip);
        }

        #region FleetProductionCenter Class
        internal class FleetProductionCenter : IProductionCenter
        {
            private readonly int _fleetId;
            private readonly BuildSlot _buildSlot;

            // ReSharper disable SuggestBaseTypeForParameter
            public FleetProductionCenter(Fleet fleet)
            {
                if (fleet == null)
                    throw new ArgumentNullException("fleet");
                _fleetId = fleet.ObjectID;
                _buildSlot = new BuildSlot();
            }
            // ReSharper restore SuggestBaseTypeForParameter

            public Fleet Fleet
            {
                get { return GameContext.Current.Universe.Objects[_fleetId] as Fleet; }
            }

            #region IProductionCenter Members
            public BuildSlot[] BuildSlots
            {
                get { return new[] { _buildSlot }; }
            }

            public int GetBuildOutput(int slot)
            {
                return Fleet.Ships.Where(o => o.ShipType == ShipType.Construction).Sum(o => o.ShipDesign.WorkCapacity);
            }

            public IList<BuildQueueItem> BuildQueue
            {
                get { return new ReadOnlyCollection<BuildQueueItem>(new List<BuildQueueItem>()); }
            }

            public void ProcessQueue() {}
            #endregion

            #region IUniverseObject Members
            public GameObjectID ObjectID
            {
                get { return Fleet.ObjectID; }
            }

            public MapLocation Location
            {
                get { return Fleet.Location; }
            }

            public GameObjectID OwnerID
            {
                get { return Fleet.OwnerID; }
            }

            public Civilization Owner
            {
                get { return Fleet.Owner; }
            }
            #endregion
        }
        #endregion
    }
    #endregion

    #region Explore Order
    [Serializable]
    public sealed class ExploreOrder : FleetOrder
    {
        private TravelRoute _route;

        public Sector TargetSector
        {
            get
            {
                if (_route.IsEmpty)
                    return null;
                return GameContext.Current.Universe.Map[_route.Steps[_route.Steps.Count - 1]];
            }
        }

        public TravelRoute Route
        {
            get { return _route; }
        }

        public override string OrderName
        {
            get { return ResourceManager.GetString("FLEET_ORDER_EXPLORE"); }
        }

        public override string Status
        {
            get { return ResourceManager.GetString("FLEET_ORDER_STATUS_EXPLORE"); }
        }

        public override bool WillEngageHostiles
        {
            get { return false; }
        }

        public override bool IsCancelledOnRouteChange
        {
            get { return true; }
        }

        internal ExploreOrder()
        {
            _route = TravelRoute.Empty;
        }

        public override FleetOrder Create()
        {
            return new ExploreOrder();
        }

        private bool AtSector(Sector sector)
        {
            if (sector == null)
                throw new ArgumentNullException("sector");
            return (Fleet.Sector == sector);
        }

        private bool GenerateRoute(Sector destination)
        {
            if (destination == null)
            {
                _route = TravelRoute.Empty;
                return false;
            }
            _route = AStar.FindPath(
                Fleet,
                new List<Sector>
                {
                    destination
                });
            return !_route.IsEmpty;
        }

        private Sector GetPathEndTurnSector()
        {
            if (_route.IsEmpty)
                return null;
            int i = 0;
            while ((i < (_route.Steps.Count - 1)) && (i < Fleet.Speed))
            {
                i++;
            }
            return GameContext.Current.Universe.Map[_route.Steps[i]];
        }

        private bool IsOtherFleetExploring(Sector lastSector)
        {
            bool result = false;
            foreach (var otherFleet in GameContext.Current.Universe.FindOwned<Fleet>(Fleet.Owner))
            {
                if ((otherFleet.Order is ExploreOrder)
                    && (((ExploreOrder)otherFleet.Order).TargetSector == lastSector))
                {
                    result = true;
                    break;
                }
            }
            return result;
        }

        private bool ExploreRange(int range)
        {
            Sector bestSector;
            Sector bestExploreSector = GetBestExplorePlot(range, out bestSector);
            if ((bestSector != null) && (bestExploreSector != null))
            {
                return true;
            }
            return false;
        }

        private Sector GetBestExplorePlot(int range, out Sector thisTurnSector)
        {
            int bestValue = 0;
            var fleet = Fleet;
            var bestRoute = TravelRoute.Empty;
            var sectors = MapHelper.GetSectorsWithinRadius(Fleet.Sector, range);
            Sector bestSector = null;
            Sector bestExploreSector = null;
            
            Algorithms.SortInPlace(
                sectors,
                delegate(Sector a, Sector b)
                {
                    return MapLocation.GetDistance(a.Location, fleet.Location).CompareTo(
                        MapLocation.GetDistance(b.Location, fleet.Location));
                });

            foreach (var sector in sectors)
            {
                int value = UnitAI.GetExploreValue(sector, fleet);
                if (value <= 0)
                    continue;
                if (UnitAI.IsEnemyUnitVisible(sector, fleet.Owner))
                    continue;
                if (IsOtherFleetExploring(sector))
                    continue;
                if (AtSector(sector) || !GenerateRoute(sector))
                    continue;
                int pathTurns = (_route.Steps.Count / fleet.Speed);
                if ((_route.Steps.Count % fleet.Speed) != 0)
                    pathTurns++;
                if (pathTurns == 1)
                {
                    value *= 16;
                    value /= 11;
                }
                if (pathTurns > range)
                    continue;
                value += UnitAI.GetExploreTurnValue(fleet);
                if (value <= bestValue)
                    continue;
                bestValue = value;
                bestSector = GetPathEndTurnSector();
                bestExploreSector = sector;
                bestRoute = _route;
            }

            _route = bestRoute;
            thisTurnSector = bestSector;
            return bestExploreSector;
        }

        private bool Explore()
        {
            int bestValue = 0;
            var fleet = Fleet;
            var map = GameContext.Current.Universe.Map;
            var mapData = GameContext.Current.CivilizationManagers[fleet.Owner].MapData;
            Sector bestSector = null;

            if ((AssignmentDuration > 0) && (TargetSector != null))
            {
                var lastSector = TargetSector;
                if (!AtSector(lastSector))
                {
                    int value = UnitAI.GetExploreValue(lastSector, fleet);
                    if ((value > 0) 
                        && !UnitAI.IsEnemyUnitVisible(lastSector, fleet.Owner)
                        && !IsOtherFleetExploring(lastSector)
                        && GenerateRoute(lastSector))
                    {
                        bestValue = value;
                        bestSector = GetPathEndTurnSector();
                    }
                }
            }

            if (bestSector == null)
            {
                const int maxMoreToCheck = 3;
                const int maxGoodOnes = 3;

                int moreToCheck = -1;

                var checkedSectors = new List<Sector>();
                var sectors = new List<Sector>();
                for (int x = 0; x < map.Width; x++)
                {
                    for (int y = 0; y < map.Height; y++)
                    {
                        if (!mapData.IsExplored(map[x,y].Location))
                            sectors.Add(map[x,y]);
                    }
                }

                //Algorithms.RandomShuffleInPlace(sectors);
                Algorithms.SortInPlace(
                    sectors,
                    (a, b) => MapLocation.GetDistance(a.Location, fleet.Location).CompareTo(
                                  MapLocation.GetDistance(b.Location, fleet.Location)));

                for (int i = 0; i < sectors.Count; i++)
                {
                    if (moreToCheck == 0)
                        break;
                    var loopSector = sectors[i];
                    if (mapData.IsExplored(loopSector.Location))
                        continue;
                    int goodOnes = 0;
                    var otherSectors = MapHelper.GetSectorsWithinRadius(loopSector, fleet.SensorRange);
                    Algorithms.RandomShuffleInPlace(otherSectors);
                    for (int j = 0; (j < otherSectors.Count) && (goodOnes <= maxGoodOnes); j++)
                    {
                        var checkSector = otherSectors[j];
                        if (checkedSectors.Contains(checkSector) || mapData.IsExplored(checkSector.Location))
                            continue;
                        checkedSectors.Add(checkSector);
                        int value = UnitAI.GetExploreValue(checkSector, fleet);
                        if (value <= 0)
                            continue;
                        if (UnitAI.IsEnemyUnitVisible(checkSector, fleet.Owner))
                            continue;
                        if (IsOtherFleetExploring(checkSector))
                            continue;
                        if (AtSector(checkSector) || !GenerateRoute(checkSector))
                            continue;
                        int pathTurns = _route.Steps.Count / fleet.Speed;
                        if ((_route.Steps.Count % fleet.Speed) != 0)
                            pathTurns++;
                        goodOnes++;
                        value /= (pathTurns + 1);
                        value += UnitAI.GetExploreTurnValue(fleet);
                        if (value <= bestValue)
                            continue;
                        if (moreToCheck < 0)
                            moreToCheck = maxMoreToCheck;
                        bestValue = value;
                        bestSector = GetPathEndTurnSector();
                    }
                }
            }

            return (bestSector != null);
        }

        protected internal override void OnTurnBeginning()
        {
            base.OnTurnBeginning();
            if (!IsAssigned)
                return;
            var lastRoute = _route;
            if (!ExploreRange(Fleet.Range))
            {
                _route = lastRoute;
                if (!Explore())
                    _route = TravelRoute.Empty;
            }
            Fleet.SetRouteInternal(_route);
        }

        protected internal override void OnTurnEnding()
        {
            base.OnTurnEnding();
            if (IsAssigned)
                Fleet.SetRouteInternal(TravelRoute.Empty);   
        }
    }
    #endregion
}
